/* $Id:server.cc jjs $ */
/*
   Copyright (C) 2007 by Jurjen Stellingwerff <jurjen@stwerff.xs4all.nl>
   Part of the Moros Project http://moros.sourceforge.net/

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.

   See the COPYING file for more details.
*/

#define DEBUG_ROUTINE "server"

#include <signal.h>
#include <time.h>
#include "../general/arguments.h"
#include "../general/log.h"
#include "../general/util.h"
#include "../general/misc.h"
#include "server.h"
#include "errno.h"
#include <fstream>

#include "../general/general.h"
#include "rule.h"
#include "aging.h"
#include "item.h"
#include "person.h"
#include "race.h"
#include "background.h"
#include "job.h"
#include "action.h"

#define CENTRAL_SERVER "127.0.0.1"

void server::data_handler(channel *ch) {
    if (ch->key=="game") {
        if (ch->peek("join")) {
            ch->next();
            join=toint(ch->value);
        }
        if (ch->peek("goal")) {
            ch->next();
            goal=toint(ch->value);
        }
        //int nr=1;
        while (ch->peek("party")) {
            ch->next();
        }
    }
}

#define setsig(c,s) {sigc::signal<void, std::string> s##_signal; \
    s##_signal.connect(sigc::mem_fun(*this, &server::s)); \
    commands[c]=s##_signal;}

server::server() {
    comm=communicate();
    cent_comm=communicate();
    cent_comm.user="";
    central_server=CENTRAL_SERVER;
	// Commands
	setsig("move", move);
	//setsig("set_terrain", set_terrain);
	//setsig("refresh", get_refresh);
	state=state_editor;
}

#define slen_x 7
#define slen_y 5

void server::move(std::string p) {
    pos tp(p);
}

/*
void server::set_terrain(std::string terrain) {
    pos &p=c_player->cp;
    area &a=worldmap->get_map(p.x, p.y);
    a.terrain=toint(terrain);
    if (c_channel->key=="f") {
        a.fields=toint(c_channel->value);
        c_channel->next();
    }
    if (c_channel->key=="a") {
        a.add=toint(c_channel->value);
        c_channel->next();
    }
}
*/

/*
void server::send_tile(pos t) {
    area& a=worldmap->get_map(t.x,t.y);
    c_channel->write("tile", tostr(t.x)+","+tostr(t.y));
    c_channel->write("t", a.terrain);
    c_channel->write("a", a.add);
    c_channel->write("f", a.fields);
    if (cities.count(t)>0) {
        city& c=cities[t];
        c_channel->write("city", c.name);
        c_channel->write("sz", c.citysize);
        c_channel->write("wsz", c.wallsize);
        c_channel->write("w", c.wall);
        c_channel->write("m", c.moat);
        if (castles.count(t)>0) {
            c_channel->write("c", castles[t].castlesize);
        }
    } else if (castles.count(t)>0) {
        castle&c=castles[t];
        c_channel->write("castle", c.name);
        c_channel->write("sz", c.castlesize);
        c_channel->write("w", c.wall);
        c_channel->write("m", c.moat);
    }
}
*/
void server::get_refresh(std::string dummy) {
/*    int x;
    int y;
    for (y=c_player->cp.y-slen_y; y<c_player->cp.y+slen_y; y++) {
        for (x=c_player->cp.x-slen_x; x<c_player->cp.x+slen_x; x++) {
            pos t(x,y);
            send_tile(t);
        }
    }
*/
}

void server::handler(channel *ch) {
    if (ch->lock) {
        if (cent_ch->payload!=0) {
            ch->write("error", "Sorry currently busy, try again");
            ch->send();
            return;
        }
        string usern;
        if (ch->key!="user") {
            ch->write("error", "Expecting user name");
            ch->send();
            return;
        }
        usern=ch->value;
        ch->next();
        /*
        //c_player=&players[usern];
        c_player->connected=false;
        c_player->ch=ch;
        if (ch->key!="session") {
            ch->write("error", "Expecting session key");
            ch->send();
            return;
        }
        c_channel=ch;
        */
        cent_ch->payload=ch;
        cent_ch->write("client", usern);
        cent_ch->write("session", ch->value);
        cent_ch->send();
        ch->next();
        //ch->payload=c_player;
        ch->lock=false;
        return;
    } else {
        //c_player=(player*)ch->payload;
    }
    /*
    if (!c_player->connected) { // not finished sending session
        dbg_calls("Waiting on central server");
        return;
    }
    */
    c_time=time(NULL);
    if (ch->finished) {
        // player not connected anymore
        return;
    }
    while (true) {
        std::string key=ch->key;
        if (commands.count(key) == 0) {
            dbg_error("Unknown command '%s' skipped", key.c_str());
        } else {
            commands[key](ch->value);
        }
        if (!ch->next()) break;
    }
   // if (c_player!=NULL) {
        //std::cout << "Finished\n";
        //c_player->last=c_time;
  //  } else {
   //     dbg_error("No login found");
   // }
    c_channel->send();
}

#define dfile(filen) (filename("~/")+filen)

void server::data_session() {
    sigc::signal<void, channel*> client_handler_signal;
    client_handler_signal.connect(sigc::mem_fun(*this, &server::client_handler));
    cent_ch=cent_comm.client(central_server, 12002, client_handler_signal);
    if (cent_ch==0) dbg_fatal("Cannot connect to central server");
    cent_ch->write("data", "get");
    cent_ch->fwrite(dfile("data.gz"));
    cent_ch->send();
    cent_ch->write("map", "get");
    cent_ch->fwrite(dfile("map.gz"));
    cent_ch->send();
    cent_comm.complete(20);
    if (!system(("gzip "+dfile("data.gz")).c_str())) {
        sigc::signal<void, channel*> data_handler_signal;
        data_handler_signal.connect(sigc::mem_fun(*this, &server::data_handler));
        comm.reading(dfile("data.gz"), data_handler_signal);
    }
    if (!system(("gzip "+dfile("map.gz")).c_str())) {
        //worldmap->load(dfile("map.gz"));
    }
}

void server::client_handler(channel *ch) {
    if (ch->key=="session") {
        c_channel=(channel*)ch->payload;
        c_channel->write("session", ch->value);
        c_channel->send();
        ch->payload=0;
        dbg_calls("Got session from central server");
    }
}

bool stopped=false;

void server::ip_addr(std::string host) {
    central_server=host;
}

void server::session_set(std::string session) {
    cent_comm.session=session;
}

void server::set_number(std::string nr) {
    cent_comm.user=nr;
}

void server::do_fork(std::string file) {
    dbg_calls("do_fork with pid file '%s'", file.c_str());
    int pid=fork();
    if (pid>0) {
        FILE *fp=fopen(file.c_str(), "w");
        if (!fp) dbg_fatal("Error: %s opening pid file: '%s'", strerror(errno), file.c_str());
        fprintf(fp,"%d\n", pid);
        fclose(fp);
        exit(0);
    }
    fclose(stdin);
    fclose(stdout);
    fclose(stderr);
}


void server::main_loop() {
    channel ch(TYPE_IMPORT, 0);
    loopSingle(import_files, file, string, list) {
        ch.fread(*file);
        ch.next();
        while (ch.key!="") {
            if (!rule_repo.load(ch) &&
              !aging_repo.load(ch) &&
              !item_repo.load(ch) &&
              !person_repo.load(ch) &&
              !race_repo.load(ch) &&
              !job_repo.load(ch) &&
              !action_repo.load(ch) &&
              !background_repo.load(ch)) {
                dbg_fatal("Unknown keypair %s: '%s'", ch.key.c_str(), ch.value.c_str());
                exit(1);
            }
        }
    }
    loopSingle(import_files, file, string, list) {
        ch.fread(*file);
        ch.next();
        while (ch.key!="") {
            if (!rule_repo.load(ch) &&
              !aging_repo.load(ch) &&
              !item_repo.load(ch) &&
              !person_repo.load(ch) &&
              !race_repo.load(ch) &&
              !job_repo.load(ch) &&
              !action_repo.load(ch) &&
              !background_repo.load(ch)) {
                dbg_fatal("Unknown keypair %s: '%s'", ch.key.c_str(), ch.value.c_str());
                exit(1);
            }
        }
    }
    if (export_file!="") {
        ofstream f;
        f.open(export_file.c_str());
        f << "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>" << endl;
        f << "<book>" << endl;
        {loopRules(i) i->xml(f);}
        f << "</book>" << endl;
        exit(0);
    }
    data_session();
    sigc::signal<void, channel*> handler_signal;
    handler_signal.connect(sigc::mem_fun(*this, &server::handler));
	comm.server(11000, handler_signal);
	while (!stopped) {
		comm.poll();
		cent_comm.poll();
		usleep(10000);
	}
	dbg_state("Server stopped");
}

void sig_int(int signum) {
	dbg_state("Signal interrupt: closing server");
	stopped = true;
}

void sig_pipe(int signum) {
	dbg_state("Signal pipe");
}

/// Set the log level

void server::log_level(std::string level) {
   dbg_level(toint(level), 0);
}

/// Set the log file

void server::log_file(std::string file) {
   dbg_init(file.c_str());
}

/// Load a test map

/*
void server::test_map(std::string file) {
    int y=0;
    unsigned int x;
    string line;
    ifstream test(file.c_str());
    while (! test.eof() ) {
      getline(test,line);
      for(x=0; x<line.length(); x++) {
          int a=-1;
          int t=-1;
          switch (line[x]) {
            case '0': t=tr_ocean;
            case '1': t=tr_deserthills;
            case '2': t=tr_grass;
            case '3': t=tr_hills;
            case '4': t=tr_mountains;
            case '5': t=tr_swamp;
            case '6': t=tr_dirt; a=ad_forest;
            case '7': t=tr_mountains; a=ad_mountainpeak;
            case '8': t=tr_coast;
          }
          if (t>-1) worldmap->get_map(x, y).terrain=t;
          if (a>-1) worldmap->get_map(x, y).add=a;
      }
      y++;
    }
    test.close();
}
*/

void server::set_import(std::string file) {
    import_files.push_back(file);
}

void server::set_export(std::string file) {
    export_file=file;
}

#define arg_sig(t,a,s) {sigc::signal<void, std::string> s##_signal; \
    s##_signal.connect(sigc::mem_fun(serv, &server::s)); \
    arg_add(t, "", "", a, s##_signal);}

int main(int argc, char* argv[]) {
    server serv=server();
	signal(SIGPIPE, sig_pipe);
    signal(SIGINT, sig_int);
    signal(SIGTERM, sig_int);
    dbg_level(DEBUG_CALLS, 0);
    dbg_init("~/.moros/server.log");
    dbg_state("Initializing");

    sigc::signal<void, std::string> dummy;
    arg_add(ARG_PROGRAM, "", "", "chatserver", dummy);
    arg_add(ARG_VERSION, "", "", "0.1", dummy);
    arg_add(ARG_DATE, "", "", "April 24 2007", dummy);
    arg_add(ARG_COPYRIGHT, "", "", "Copyright (C) 2007, Jurjen Stellingwerff <jurjen@stwerff.xs4all.nl>", dummy);
    arg_add(ARG_COPYRIGHT, "", "", "This is free software.  You may redistribute copies of it under the terms of", dummy);
    arg_add(ARG_COPYRIGHT, "", "", "the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.", dummy);
    arg_add(ARG_COPYRIGHT, "", "", "There is NO WARRANTY, to the extent permitted by law.", dummy);
    arg_add(ARG_USAGE, "", "", "[OPTION]...", dummy);
    arg_add(ARG_DESCR, "", "*", "Chat server for the moros game.", dummy);
    arg_sig("i", "Import rule defintion files", set_import);
    arg_extra("import", "FILE");
    arg_sig("e", "Export xml file", set_export);
    arg_extra("export", "FILE");
    arg_sig("G", "Game number", set_number);
    arg_extra("game_name", "GAME");
    arg_sig("C", "Central server ip address.", ip_addr);
    arg_extra("central", "HOST");
    arg_sig("S", "Session ID with central server.", session_set);
    arg_extra("session", "ID");
    arg_sig("P", "Fork the process to the background leave the process id in the give pid file.", do_fork);
    arg_extra("pid_file", "FILE");
    arg_sig("L", "Logfile location, default: /var/log/moros_central.log", log_file);
    arg_extra("log_file", "FILE");
    arg_sig("l", "Loglevel 0-Only errors, 1-state info (default), 2-comm, 3-data, 4-calls", log_level);
    arg_extra("log_level", "LEVEL");
    //arg_sig("m", "Read test map", test_map);
    //arg_extra("read_map", "FILE");
    arg_add(ARG_BUGS, "", "", "info@moroshq.org", dummy);
    arg_parse(argc, argv);
	serv.main_loop();
	return 0;
}
